package edu.imac.angryfood.world;

import java.util.concurrent.CopyOnWriteArrayList;

import org.jbox2d.collision.AABB;
import org.jbox2d.common.Vec2;
import org.newdawn.slick.Image;

import edu.imac.angryfood.component.AbstractProjectile;
import edu.imac.angryfood.shapes.Rectangle;

/**
 * The central object of the simulation. The world contains the bodies (and joints) which
 * model the world and react to the physics engine.
 * 
 */
public class World {
	/** The default gravity applied if none is specified (-10) */
	public static final float DEFAULT_GRAVITY = -10;
	/** The default left hand bound of the physics world if none is specified (-200) */
	public static final float DEFAULT_LEFT_BOUND = -200;
	/** The default right hand bound of the physics world if none is specified (200) */
	public static final float DEFAULT_RIGHT_BOUND = 200;
	/** The default top bound of the physics world if none is specified (-200) */
	public static final float DEFAULT_TOP_BOUND = -200;
	/** The default bottom bound of the physics world if none is specified (200) */
	public static final float DEFAULT_BOTTOM_BOUND = 200;
	/** The default number of iteration used in the integration if none specified (10) */
	public static final float DEFAULT_ITERATIONS = 10;

	
	/** The JBox2D world this World object is wrapping */
	private org.jbox2d.dynamics.World jboxWorld; 
	/** The list of bodies added to the world */
	private CopyOnWriteArrayList<Body> bodies = new CopyOnWriteArrayList<Body>(); 
	
	public Image backgroundImage;
	public Image skyImage;
	
	private CopyOnWriteArrayList<AbstractProjectile> projectiles= new CopyOnWriteArrayList <AbstractProjectile> ();
	
	/** The number of iterations to integrate over */
	private int iterations;

	public World(int iterations) {
		this(DEFAULT_LEFT_BOUND,DEFAULT_TOP_BOUND,DEFAULT_RIGHT_BOUND,DEFAULT_BOTTOM_BOUND
				 ,DEFAULT_GRAVITY, iterations);
	}
	
	public World() {
		this(DEFAULT_LEFT_BOUND,DEFAULT_TOP_BOUND,DEFAULT_RIGHT_BOUND,DEFAULT_BOTTOM_BOUND
			 ,DEFAULT_GRAVITY, DEFAULT_ITERATIONS);
	}

	public World(float worldWidth, float worldHeight) {
		this(-worldWidth/2,-worldHeight/2,worldWidth/2,worldHeight/2
			 ,DEFAULT_GRAVITY, DEFAULT_ITERATIONS);
	}
	
	/**
	 * Create a new world simulation
	 * @param x1 left limit of the physics world
	 * @param y1 high limit of the physics world
	 * @param x2 right limit of the physics world
	 * @param y2 low limit of the physics world
	 * @param g gravity
	 * @param iterations The number of iterations to integrate over
	 */
	public World(float x1, float y1, float x2, float y2, float g, float iterations) {
		this.iterations = (int) iterations;
		AABB m_worldAABB = new AABB();
		m_worldAABB.lowerBound = new Vec2(x1, y1);
		m_worldAABB.upperBound = new Vec2(x2, y2);
		Vec2 gravity = new Vec2(0.0f, g);
		boolean doSleep = true;
		jboxWorld = new org.jbox2d.dynamics.World(m_worldAABB, gravity, doSleep);
	
	}
	
	/**
	 * Return an array of bodies
	 * @return bodies
	 */
	public CopyOnWriteArrayList<Body> getBodies() {
		return bodies;
	}

	/**
	 * Return an array of projectiles
	 * @return projectiles
	 */
	public CopyOnWriteArrayList<AbstractProjectile> getProjectiles() {
		return projectiles;
	}

	/**
	 * Get the JBox2D world that is being wrapped
	 * @return The JBox2D world that is being wrapped
	 */
	public org.jbox2d.dynamics.World getJBoxWorld() {
		return jboxWorld;
	}

	/**
	 * Update wit a step
	 * @param timeStep the time to step
	 */
	public void update(float timeStep) {
		jboxWorld.step(timeStep, iterations); 
	}
	
	/**
	 * Add the body to the world
	 * @param body to add
	 */
	public void add(Body body) {
		body.addToWorld(this); 
		bodies.add(body);
		if (body instanceof AbstractProjectile){
			AbstractProjectile proj = (AbstractProjectile) body;
			projectiles.add(proj);
		}
	}

	/**
	 * Remove a body to the world
	 * @param body to remove
	 */
	public void remove(Body body) {
		
		body.removeFromWorld(this);
		bodies.remove(body);
		if (body instanceof AbstractProjectile){
			AbstractProjectile proj = (AbstractProjectile) body;
			projectiles.remove(proj);
		}
	}
	
	/**
	 * Count the number of bodies
	 * @return the number of bodies
	 */
	public int getBodyCount() {
		return bodies.size();
	}
	
	/**
	 * Return a body at the position index
	 * @param index the position
	 * @return a body
	 */
	public Body getBody(int index) {
		return bodies.get(index);
	}

	/**
	 * Draw a static plateform
	 * @param width the width of the plateform
	 * @param height the height of the plateform
	 * @param x the position in x of the plateform
	 * @param y the position in y of the plateform
	 * @param angle of the plateform
	 */
	public void rectangleStaticPlateform(float width, float height, float x, float y, float angle) {
		Body platforme = new Body(new Rectangle(width, height), x, y, true);
		platforme.setRestitution(0.2f);
		platforme.setFriction(0.1f);
		this.add(platforme);
		platforme.setRotation((float)Math.toRadians(angle));
	}
}
